﻿using System;
using System.IO;
using System.Collections.Generic;
using AzukiMap.Tools.Logger;
using Geotools.Geometries;
using Geotools.IO;
using System.Collections;

namespace AzukiMap.Request
{
    public class PostGisVectorParser : IEnumerable
    {
        protected BinaryReader _geomReader;
        protected GeometryFactory _geoFactory;
        protected int _currentReadingRecord;
        protected int _numOfObjects;
        protected int _errorCode;
        PostGisRecord[] _records;
        Dictionary<int, string> _errorMessages;

        public BinaryReader GeomReader
        {
            get { return _geomReader; }
        }
 
        public int NumOfObjects
        {
            get { return _numOfObjects; }
        }

        public int ErrorCode
        {
            get { return _errorCode; }
        }

        public PostGisVectorParser(BinaryReader geomReader)
        {
            Logger.Debug(
                "PostGisDataParser.PostGisDataParser(): "
                + " geomReader.BaseStreamLength: " + geomReader.BaseStream.Length
                );

            _geomReader = geomReader;
            _currentReadingRecord = 0;
            PrecisionModel pm = new PrecisionModel();
            _geoFactory = new GeometryFactory(pm, -1);
            ParseHeader();
            ParseRecords();
            InitErrorMessages();
        }

        /// <summary>
        /// The respose header is only a number. 
        /// If the number is positive value　or zero, it means number of records. 
        /// If it has minus value, it is a error code. 
        /// </summary>
        protected virtual void ParseHeader()
        {
            if (_geomReader.BaseStream.Position != 0)
            {
                Logger.Error("PostGisDataParser: Reader position is not head.");
                throw new Exception("Reader position is not head.");
            }
            int header = _geomReader.ReadInt32();
            // has data
            if (header >= 0)
            {
                _numOfObjects = header;
                _errorCode = 0;
                try
                {
                    _records = new PostGisRecord[_numOfObjects];
                }
                catch (OutOfMemoryException ex)
                {
                    Logger.Fatal("PostGisDataParser.ParseHeader(): number of result objects is wrong. ", ex);
                }

                Logger.Debug(
                    "PostGisDataParser.ParseHeader(): "
                    + " header = " + header.ToString()
                    );

            }
            // has error
            else
            {
                _numOfObjects = 0;
                _errorCode = header;
                _records = new PostGisRecord[] { };

                Logger.Debug(
                    "PostGisDataParser.ParseHeader(): No Records."
                    + " header = " + header.ToString()
                    );
            }
        }

        protected virtual void ParseRecords()
        {
            if (_errorCode == 0)
            {
                while (hasNext())
                {
                    _records[_currentReadingRecord] =
                        GetNextRecord();
                }
            }
        }

        void InitErrorMessages()
        {
            _errorMessages = new Dictionary<int, string>();
            _errorMessages.Add(0,"No Error");
            _errorMessages.Add(-1,"Database connection has a problem");
            _errorMessages.Add(-2,"Query has a error");
            _errorMessages.Add(-3,"Query process error");
            _errorMessages.Add(-11,"db parameter does not set");
            _errorMessages.Add(-12,"table parameter does not set");
            _errorMessages.Add(-13,"column parameter does net set");
            _errorMessages.Add(-14,"extent parameter does net set");
        }

        public bool HasError()
        {
            bool hasError = (_errorCode != 0);
            return hasError;
        }

        public string GetErrorMessage()
        {
            return _errorMessages[_errorCode];
        }

        public bool hasNext()
        {
            return (_currentReadingRecord < _numOfObjects);
        }

        protected PostGisRecord GetNextRecord()
        {
            if (hasNext())
            {
                PostGisRecord record = new PostGisRecord();
                record.ThemeId = GetThemeIdFromBinRead();
                record.Geometry = GetGeometryFromBinReader();

                _currentReadingRecord++;
                return record;
            }
            else
            {
                Logger.Warn("PostGisDataParser: There is no record to read in the stream.");
                throw new NoNextRecordException("There is no record to read in the stream.");
            }
        }

        int GetThemeIdFromBinRead()
        {
            return _geomReader.ReadInt32();
        }

        protected virtual GTGeometry GetGeometryFromBinReader()
        {
            GeometryWKBReader reader = new GeometryWKBReader(_geoFactory);
            GTGeometry geom = reader.Create(_geomReader);
            return geom;
        }

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new PostGisDataEnum(_records);
        }

        #endregion
    }

    public class PostGisDataEnum : IEnumerator
    {
        PostGisRecord[] _records;
        int _pos = -1;
        bool _disposed = false;
       
        public PostGisDataEnum(PostGisRecord[] records)
        {
            _records = records;
        }

        #region IEnumerator Members

        public object Current
        {
            get
            {
                try
                {
                    return _records[_pos];
                }
                catch (IndexOutOfRangeException ex)
                {
                    Logger.Error("PostGisDataEnum: IndexOutOfRangeException.", ex);
                    throw new InvalidOperationException();
                }
            }
        }

        public bool MoveNext()
        {
            _pos++;
            return (_pos < _records.Length);
        }

        public void Reset()
        {
            _pos = -1;
        }

        #endregion
    }


    public class NoNextRecordException : Exception
    {
        public NoNextRecordException(string message)
            : base(message)
        {
        }
    }

    public struct PostGisRecord
    {
        public int ThemeId;
        public GTGeometry Geometry;
    }
}
